package jflowmap.views.flowstrates;

import java.awt.Color;
import java.awt.Font;
import java.awt.geom.Line2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import jflowmap.FlowMapGraph;
import jflowmap.util.Colors;
import jflowmap.util.piccolo.PNodes;
import jflowmap.util.piccolo.PPaths;
import prefuse.data.Edge;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;

/**
 * @author Ilya Boyandin
 */
class CentroidTimeSlider extends PNode {

	private static class ValueHelper {
		public double value;
		public boolean partiallyMissing;
		public boolean missing;
	}
	
	private static final int MARGIN_TOP = 5;
	private final Centroid centroid;
	private final double width = 160;
	private final double height = 130;
	
	private static final double minHeight = 0;
	private static final double maxHeight = 130;
	
	private final List<String> columns;
	private final List<String> rows;
	private final Iterable<Edge> edges;
	private final static Font TYPE_FONT = new Font("Arial", Font.BOLD, 11);
	private final static Color BAR_COLOR_MISSING = new Color(240, 240, 240);
	
	private Map<String, Double> columnMaxs;
	private Map<String, Double> columnMins;

	public CentroidTimeSlider(Centroid centroid) {
		this.centroid = centroid;

		FlowstratesView view = centroid.getView();
		FlowMapGraph fmg = view.getFlowMapGraph();
		columns = fmg.getEdgeWeightAttrs();
		edges = calculateEdges();
		rows = calculateRows();
		calculateColumnMinMaxs();

		int numCols = columns.size();
		if (numCols > 0) {
			double w = width / numCols - 2;
			PBounds cb = centroid.getFullBoundsReference();
			double y = cb.getMaxY() + 2;
			
			for (String type : rows) {
				double x = cb.getX() + (cb.getWidth() - width)/2;
				
				PText text = new PText(type);
				text.setFont(TYPE_FONT);
				text.setBounds(x-text.getWidth()-5, y+maxHeight/2-text.getHeight()/2, text.getWidth(), text.getHeight());
				addChild(text);
								
				for (String attr : columns) {
					PPath rect = PPaths.rect(x, y, w, height);
					rect.setName(attr + ":" + type);
					rect.setStroke(null);
					rect.setPaint(Colors.getColorFor(type, "flowstrates"));
					addChild(rect);
					x += w + 2;
				}

				Line2D shape = new Line2D.Double();
				shape.setLine(0, 0, width, 0);
				PPath line = new PPath(shape);
				line.setStroke(new PFixedWidthStroke(2));
				line.setStrokePaint(Color.BLACK);	
				final double zeroPosY = columnMins.get(type) < 0 ? maxHeight/2 : maxHeight;				
				line.setBounds(x-(w+2)*numCols-2, y+zeroPosY, width+2, line.getHeight());
				addChild(line);				
				line.setName("__lineZero");
				
				PText zeroValue = new PText("0.0");
				zeroValue.setX(x + 5);
				zeroValue.setY(y+zeroPosY - zeroValue.getHeight()/2);
				addChild(zeroValue);
				zeroValue.setName("__textZero");
				
				Line2D maxLine = new Line2D.Double();
				maxLine.setLine(0, 0, width, 0);
				PPath maxLinePath = new PPath(maxLine);
				maxLinePath.setStroke(new PFixedWidthStroke(1));
				maxLinePath.setStrokePaint(new Color(150, 150, 150, 150));
				final double posY = columnMins.get(type) < 0 ? -maxHeight/2 : 0;
				maxLinePath.setBounds(x-(w+2)*numCols-2, y+posY, width+2, maxLinePath.getHeight());
				addChild(maxLinePath);
				maxLinePath.setName("__lineMax");
				
				PText maxValue = new PText(Double.toString((int)(columnMaxs.get(type)*10)/10.0));
				maxValue.setX(x + 5);
				maxValue.setY(y - maxValue.getHeight()/2);
				addChild(maxValue);
				maxValue.setName("__textMax");
				
				Line2D halfLine = new Line2D.Double();
				halfLine.setLine(0, 0, width, 0);
				PPath halfLinePath = new PPath(halfLine);
				halfLinePath.setStroke(new PFixedWidthStroke(1));
				halfLinePath.setStrokePaint(new Color(150, 150, 150, 150));
				final double halfPosY = columnMins.get(type) < 0 ? maxHeight/4 : maxHeight/2;
				halfLinePath.setBounds(x-(w+2)*numCols-2, y+halfPosY, width+2, halfLinePath.getHeight());
				addChild(halfLinePath);
				halfLinePath.setName("__lineHalf");
				
				PText halfValue = new PText(Double.toString((int)(columnMaxs.get(type)*5)/10.0));
				halfValue.setX(x + 5);
				halfValue.setY(y + halfPosY - halfValue.getHeight()/2);
				addChild(halfValue);
				halfValue.setName("__textHalf");
				
				y += maxHeight + text.getHeight() + 2;
			}
		}

		update();
	}

	private void calculateColumnMinMaxs() {
		columnMaxs = new HashMap<String, Double>();
		columnMins = new HashMap<String, Double>();
		
		for (String type: rows) {
			for (String column: columns) {
				final double value = getAggValueFor(type, column).value;
				if (columnMaxs.containsKey(type)) {
					if (value > columnMaxs.get(type)) {
						columnMaxs.put(type, value);
					}
				} else columnMaxs.put(type, value);
				if (columnMins.containsKey(type)) {
					if (value < columnMins.get(type)) {
						columnMins.put(type, value);
					}
				} else columnMins.put(type, value);
			}
		}
	}

	private Iterable<Edge> calculateEdges() {

		return Iterables.filter(centroid.getView().getVisibleEdges(),
			new Predicate<Edge> () {
				public boolean apply(Edge edge) {
					if (edge.getSourceNode() == centroid.getNode() || edge.getTargetNode() == centroid.getNode()) {
						return true;
					}
					return false;
				}
		});
	}

	private List<String> calculateRows() {
		final String typeAttr = centroid.getView().getFlowMapGraph().getAttrSpec().getFlowTypeAttr();
		
		final Set<String> types = new HashSet<String>();
		for (Edge edge: edges) {
			if (edge.canGetString(typeAttr)) {
				types.add(edge.getString(typeAttr));
			}
		}
		return Lists.newArrayList(types);
	}

	public void update() {
		for (PPath rect : PNodes.childrenOfType(this, PPath.class)) {
			final String name = rect.getName();
			if (name.startsWith("__line") || name.startsWith("__text")) continue;
			
			final String[] parts = name.split(":");
			final String attr = parts[0];
			final String type = parts[1];

			final double max = columnMaxs.get(type);
			final ValueHelper vh = getAggValueFor(type, attr);
			
			final double zeroPosY = columnMins.get(type) < 0 ? maxHeight/2 : maxHeight;
			
			final double height = Math.abs(zeroPosY*vh.value/max);
			
			rect.setHeight(height);
			if (vh.partiallyMissing) {
				rect.setPaint(Colors.getLightColorFor(type, "flowstrates"));
			}
			if (vh.missing) {
				rect.setPaint(BAR_COLOR_MISSING);
				rect.setHeight(maxHeight);
				rect.translate(0, columnMins.get(type) < 0 ? maxHeight/2 : 0);
			}
			
			rect.translate(0, -((vh.value < 0 ? 0 : rect.getHeight()) - zeroPosY));
		}
	}

	private ValueHelper getAggValueFor(final String type, final String attr) {
		final String typeAttr = centroid.getView().getFlowMapGraph().getAttrSpec().getFlowTypeAttr();
		
		Iterable<Edge> edges = Iterables.filter(this.edges, new Predicate<Edge>() {
			@Override
			public boolean apply(Edge edge) {
				if (edge.canGet(typeAttr, String.class) && 
					edge.getString(typeAttr).equals(type)) {
					return true;
				}
				return false;
			}		
		});
		
		ValueHelper vh = new ValueHelper();
		vh.missing = true;
		
		for (Edge edge: edges) {
			if (edge.canGetDouble(attr)) {
				double value = edge.getDouble(attr);
				if (!Double.isNaN(value)) {
					vh.value += value;
					vh.missing = false;
				} else {
					vh.partiallyMissing = true;
				}
			}
		}
		return vh;			
	}

}
